﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;

namespace GeoAzure.Core.Tests
{
    [TestClass]
    public class GeoRectangleFixture
    {
        [TestMethod]
        public void ContainsReturnsTrueOnContainedLocation()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));

            Assert.IsTrue(r1.Contains(Location.FromDegrees(5.0, -5.0)));
        }

        [TestMethod]
        public void ContainsReturnsFalseOnOutsideLocation()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));

            Assert.IsFalse(r1.Contains(Location.FromDegrees(-15.0, -15.0)));
        }

        [TestMethod]
        public void ContainsReturnsTrueOnRectangleEdgeLocation()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));

            Assert.IsTrue(r1.Contains(Location.FromDegrees(5.0, -10.0)));
        }

        [TestMethod]
        public void CanDetectFirstQuadrantIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));
            var r2 = GeoRectangle.Create(Location.FromDegrees(15.0, -15.0), Location.FromDegrees(5.0, -5.0));

            Assert.IsTrue(r1.Intersects(r2));
        }

        [TestMethod]
        public void CanDetectSecondQuadrantIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));
            var r2 = GeoRectangle.Create(Location.FromDegrees(15.0, -5.0), Location.FromDegrees(5.0, 5.0));

            Assert.IsTrue(r1.Intersects(r2));
        }

        [TestMethod]
        public void CanDetectThirdQuadrantIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));
            var r2 = GeoRectangle.Create(Location.FromDegrees(5.0, -5.0), Location.FromDegrees(-5.0, 5.0));

            Assert.IsTrue(r1.Intersects(r2));
        }

        [TestMethod]
        public void CanDetectFourthQuadrantIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));
            var r2 = GeoRectangle.Create(Location.FromDegrees(5.0, -15.0), Location.FromDegrees(-5.0, -5.0));

            Assert.IsTrue(r1.Intersects(r2));
        }

        [TestMethod]
        public void CanDetectFullInclusionIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));
            var r2 = GeoRectangle.Create(Location.FromDegrees(8.0, -8.0), Location.FromDegrees(3.0, -3.0));

            Assert.IsTrue(r1.Intersects(r2));
        }

        [TestMethod]
        public void CanDetectWhenNoIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));
            var r2 = GeoRectangle.Create(Location.FromDegrees(10.0, 10.0), Location.FromDegrees(0.0, 20.0));

            Assert.IsFalse(r1.Intersects(r2));
        }

        [TestMethod]
        public void RectangleIntersectsItself()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(10.0, -10.0), Location.FromDegrees(0.0, 0.0));

            Assert.IsTrue(r1.Intersects(r1));
        }

        [TestMethod]
        public void IntersectsDetectCrossLikeIntersection()
        {
            var r1 = GeoRectangle.Create(Location.FromDegrees(2d, 0d), Location.FromDegrees(1d, 4d));
            var r2 = GeoRectangle.Create(Location.FromDegrees(3d, 1d), Location.FromDegrees(0d, 3d));
            Assert.IsTrue(r1.Intersects(r2));

            GeoRectangle intersection = r1.IntersectionWith(r2).Value;

            Assert.AreEqual(GeoRectangle.Create(Location.FromDegrees(2d, 1d), Location.FromDegrees(1d,3d)), intersection);
        }

        [TestMethod]
        public void MinimumBoundingRectangleWithNoLocationsReturnsNullRectangle()
        {
            var locations = new Location[0];
            var boundingRect = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            Assert.AreEqual(GeoRectangle.NullRectangle, boundingRect);
        }

        [TestMethod]
        public void CanCreateMinimumBoundingRectangleByInflatingEast()
        {
            var topLeft = Location.FromDegrees(10.0, 170.0);
            var bottomRight = Location.FromDegrees(0.0, 175.0);
            var otherLocation = Location.FromDegrees(0.0, -175.0);

            var locations = new[] { topLeft, bottomRight, otherLocation };
            GeoRectangle minimumBoundingRectangle = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            GeoRectangle expected = GeoRectangle.Create(new Location(topLeft.Latitude, otherLocation.Longitude), bottomRight);

            Assert.AreEqual(expected, minimumBoundingRectangle);
            Assert.IsTrue(locations.All(minimumBoundingRectangle.Contains));
        }

        [TestMethod]
        public void CanCreateMinimumBoundingRectangleByInflatingWest()
        {
            var topLeft = Location.FromDegrees(10.0, 170.0);
            var bottomRight = Location.FromDegrees(0.0, 175.0);
            var otherLocation = Location.FromDegrees(0.0, 160.0);

            var locations = new[] { topLeft, bottomRight, otherLocation };
            GeoRectangle minimumBoundingRectangle = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            GeoRectangle expected = GeoRectangle.Create(new Location(topLeft.Latitude, otherLocation.Longitude), bottomRight);

            Assert.AreEqual(expected, minimumBoundingRectangle);
            Assert.IsTrue(locations.All(minimumBoundingRectangle.Contains));
        }

        [TestMethod]
        public void CanCreateMinimumBoundingRectangleByInflatingNorth()
        {
            var topLeft = Location.FromDegrees(10.0, 170.0);
            var bottomRight = Location.FromDegrees(0.0, 175.0);
            var otherLocation = Location.FromDegrees(15.0, 172.0);

            var locations = new[] { topLeft, bottomRight, otherLocation };
            GeoRectangle minimumBoundingRectangle = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            GeoRectangle expected = GeoRectangle.Create(new Location(otherLocation.Latitude, topLeft.Longitude), bottomRight);

            Assert.AreEqual(expected, minimumBoundingRectangle);
            Assert.IsTrue(locations.All(minimumBoundingRectangle.Contains));
        }

        [TestMethod]
        public void CanCreateMinimumBoundingRectangleByInflatingSouth()
        {
            var topLeft = Location.FromDegrees(10.0, 170.0);
            var bottomRight = Location.FromDegrees(0.0, 175.0);
            var otherLocation = Location.FromDegrees(-15.0, 172.0);

            var locations = new[] { topLeft, bottomRight, otherLocation };
            GeoRectangle minimumBoundingRectangle = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            GeoRectangle expected = GeoRectangle.Create(topLeft, new Location(otherLocation.Latitude, bottomRight.Longitude));

            Assert.AreEqual(expected, minimumBoundingRectangle);
            Assert.IsTrue(locations.All(minimumBoundingRectangle.Contains));
        }

        [TestMethod]
        public void CanCreateMinimumBoundingRectangleWithNoInflation()
        {
            var topLeft = Location.FromDegrees(10.0, 170.0);
            var bottomRight = Location.FromDegrees(0.0, 175.0);
            var otherLocation = Location.FromDegrees(5.0, 172.0);

            var locations = new[] { topLeft, bottomRight, otherLocation };
            GeoRectangle minimumBoundingRectangle = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            GeoRectangle expected = GeoRectangle.Create(topLeft, bottomRight);

            Assert.AreEqual(expected, minimumBoundingRectangle);
            Assert.IsTrue(locations.All(minimumBoundingRectangle.Contains));
        }

        [TestMethod]
        public void MinimumBoundingRectangleContainsAllLocations()
        {
            var locations = TestObjectFactory.CreateRandomLocations(1000000);

            GeoRectangle geoRectangle = GeoRectangle.CreateMinimumBoundingRectangle(locations);

            Assert.IsTrue(locations.All(geoRectangle.Contains));
        }

        [TestMethod]
        public void CanCalculateAreaOfSmallRectangle()
        {
            // small rectangle around my house
            var smallRectangle = GeoRectangle.Create(Location.FromDegrees(50.0 + (42.230 / 60.0), 4.0 + (31.095 / 60.0)),
                                                     Location.FromDegrees(50.0 + (42.142 / 60.0), 4.0 + (31.337 / 60.0)));

            const double expected = 284.0 * 163.0;
            double actual = smallRectangle.Area;
            Assert.IsTrue(actual.IsBetween(expected * .995, expected * 1.005));
        }

        //[TestMethod, Ignore]
        //public void CanCalculateEarthArea()
        //{
        //    // about 510072000 km2
        //    const double expected = 510072000d * 1000d * 1000d;

        //    double area = GeoRectangle.Earth.Area;
        //    Assert.IsTrue(area.IsBetween(expected * 0.995, expected * 1.005));
        //}

        [TestMethod]
        public void CanCalculatePerimeterWithLatitudeOnly()
        {
            // rectangle with no longitude span (flat) and one minute latitude
            // we should have 2 nautical miles of perimeter
            var smallRectangle = GeoRectangle.Create(Location.FromDegrees(50d + (1d / 60d), 4d),
                                                     Location.FromDegrees(50d, 4d));

            var perimeter = smallRectangle.Perimeter;

            Assert.IsTrue(perimeter.IsBetween(2d * Constants.NauticalMile * 0.995d, 2d * Constants.NauticalMile * 1.005d));
        }

        [TestMethod]
        public void CanCalculatePerimeterWithLongitudeOnlyAtEquator()
        {
            // rectangle with no latitude span (flat), one minute longitude, at the equator
            // we should have 2 nautical miles of perimeter
            var smallRectangle = GeoRectangle.Create(Location.FromDegrees(0d, 4d),
                                                     Location.FromDegrees(0d, 4d + (1d / 60d)));

            var perimeter = smallRectangle.Perimeter;

            Assert.IsTrue(perimeter.IsBetween(2d * Constants.NauticalMile * 0.995d, 2d * Constants.NauticalMile * 1.005d));
        }

        [TestMethod]
        public void RectangleDoesNotInflateWhenOtherRectangleIsFUllyContained()
        {
            var initialRectangle = GeoRectangle.Create(Location.FromDegrees(40d, 0d), Location.FromDegrees(0d, 40d));

            Assert.AreEqual(initialRectangle, initialRectangle.EnsureContainsRectangle(GeoRectangle.Create(Location.FromDegrees(30d, 10d), Location.FromDegrees(10d, 30d))));
        }

        [TestMethod]
        public void WhenOtherRectangleContainsThisOtherIsReturned()
        {
            var initialRectangle = GeoRectangle.Create(Location.FromDegrees(40d, 0d), Location.FromDegrees(0d, 40d));

            var other = GeoRectangle.Create(Location.FromDegrees(40d, -10d), Location.FromDegrees(0d, 40d));
            //var other = GeoRectangle.Create(new Location(-10d, 30d), new Location(30d, 10d));
            var inflated = initialRectangle.EnsureContainsRectangle(other);
            Assert.AreNotEqual(initialRectangle, inflated);
            Assert.AreEqual(other, inflated);
           
        }

        [TestMethod]
        public void RectangleInflatesToContainOther()
        {
            var initialRectangle = GeoRectangle.Create(Location.FromDegrees(40d, 0d), Location.FromDegrees(0d, 40d));

            var other = GeoRectangle.Create(Location.FromDegrees(30d, -10d), Location.FromDegrees(10d, 30d));
            var inflated = initialRectangle.EnsureContainsRectangle(other);
            //Assert.AreNotEqual(initialRectangle, inflated);
            Assert.IsTrue(inflated.Area > initialRectangle.Area);
            Assert.IsTrue(inflated.Contains(other));
           
        }
    }
}
